/************************************************************************************************
 * @name    SqQueue.c
 * @brief   这是循环队列的代码案例，主要学习循环队列的数据元素的增删改查，并设计API接口
 * @date    2024/12/30
 * @anchor  boluzs@163.com
 * @note
 *          1.循环队列是采用连续的内存空间进行设计，所以底层是基于顺序表实现
 *          2.为了方便区分队满和队空，所以采用空间换时间的方案，人为舍弃最后一个空间
 *          3.先进先出，需要利用队尾下标和队首下标来来进行入队和出队操作
 *          4.由于需要申请一块连续的内存，为了避免下标越界，所以(下标+1)%队列容量
 * @version 1.0.0
 *
 * 版权声明
 * CopyRight (c)   2024-2025   boluzs@163.com    All Right Reciver
 *
 *************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
typedef int DataType_t; // 用于说明循环队列数据元素的类型 提高程序的可移植性

typedef struct SqQueue
{
    unsigned int Size; // 用于记录循环队列的容量
    DataType_t *Addr;  // 用于记录循环队列的起始地址
    int Rear;          // 用于记录循环队列的队尾下标
    int Front;         // 用于记录循环队列的队首下标
} SqQueue_t, *SqQueue_p;

/************************************************************************************************
 *       @name   SqQueue_Create
 *       @brief  创建循环队列
 *       @param  Size: 指循环队列的容量
 *       @note   None
 *       @retval 函数调用成功，返回指向SqlList_t类型的指针
 *************************************************************************************************/
SqQueue_p SqQueue_Create(unsigned int Size)
{
    // 1.为管理循环队列的结构体申请堆内存并做错误处理
    // calloc函数返回一个void*类型的指针;
    SqQueue_p Pmanager = (SqQueue_p)calloc(1, sizeof(SqQueue_t));
    if (NULL == Pmanager)
    {
        fprintf(stderr, "Calloc For SqQueue_t Error,errno = %d,%s\n", errno, strerror(errno));
        exit(-1); // 导致程序终止
    }

    // 2.为循环队列的数据元素申请堆内存，并进行错误处理
    DataType_t *Padder = (DataType_t *)calloc(Size, sizeof(DataType_t));
    if (NULL == Padder)
    {
        // perror("Calloc For SqQueue_t Error");
        fprintf(stderr, "Calloc For Data Element Error,errno = %d,%s\n", errno, strerror(errno));
        exit(-1); // 导致程序终止
    }

    // 3.需要对循环队列的管理结构体成员进行赋值
    Pmanager->Size = Size;   // 记录容量
    Pmanager->Rear = 0;      // 记录下标
    Pmanager->Front = 0;     // 记录下标
    Pmanager->Addr = Padder; // 记录地址

    // 4.返回地址，并输出调试信息
    printf("Queue Create Successfully!\n");
    return Pmanager;
};

/************************************************************************************************
 *       @name   SqQueue_IsFull
 *       @brief  判断循环队列是否已满
 *       @param  Pmanager: 指记录循环队列属性的结构体指针
 *       @note   队首下标==(队尾下标+1)%队列容量
 *       @retval 循环队列已满，返回true，循环队列已满，返回false
 *************************************************************************************************/
bool SqQueue_IsFull(SqQueue_p Pmanager)
{
    return (Pmanager->Front == (Pmanager->Rear + 1) % (Pmanager->Size)) ? true : false;
}

/************************************************************************************************
 *       @name   SqQueue_IsEmpty
 *       @brief  判断循环队列是否已满
 *       @param  Pmanager: 指记录循环队列属性的结构体指针
 *       @note   None
 *       @retval 循环队列为空，返回true，循环队列不为空，返回false
 *************************************************************************************************/
bool SqQueue_IsEmpty(SqQueue_p Pmanager)
{
    return (Pmanager->Rear == Pmanager->Front) ? true : false;
}

/************************************************************************************************
 *       @name   SqQueue_Enqueue
 *       @brief  向循环队列的尾部插入新元素
 *       @param  Pmanager: 指记录循环队列属性的结构体指针
 *       @param  Data: 指待插入循环队列的数据元素的值
 *       @note   插入元素之前需要先检查循环队列是否已满
 *       @retval 插入成功，返回true，插入失败，返回false
 *************************************************************************************************/
bool SqQueue_Enqueue(SqQueue_p Pmanager, DataType_t Data)
{
    // 为了提高程序的可靠性，所以需要对指针进行备份
    SqQueue_p P = Pmanager;

    // 1、检查参数有效性
    if (NULL == Pmanager)
    {
        fprintf(stderr, "Insert Error, Argument Is Invalid\n");
        return false;
    }

    // 2、检查循环队列容量
    if (SqQueue_IsFull(P))
    {
        fprintf(stderr, "Insert Error, SqQueue Is Full\n");
        return false;
    }

    // 3、循环队列插入元素,更新循环队列的属性
    P->Addr[P->Rear] = Data;
    P->Rear=(P->Rear+1) % P->Size;
    return true;
}

/************************************************************************************************
 *       @name   SqQueue_Dequeue
 *       @brief  删除循环队列的尾部元素
 *       @param  Pmanager: 指记录循环队列属性的结构体指针
 *       @note   删除元素之前需要先检查循环队列是否为空
 *       @retval 出栈返回元素值
 *************************************************************************************************/
DataType_t SqQueue_Dequeue(SqQueue_p Pmanager)
{
    // 为了提高程序的可靠性，所以需要对指针进行备份
    SqQueue_p P = Pmanager;

    // 1、检查参数有效性
    if (NULL == Pmanager)
    {
        fprintf(stderr, "Delete Error, Argument Is Invalid\n");
        exit(-1);
    }

    // 2、检查循环队列容量
    if (SqQueue_IsEmpty(P))
    {
        fprintf(stderr, "Delete Error, SqQueue Is Empty\n");
        exit(-1);
    }

    // 3、备份需要弹出的元素
    DataType_t temp = Pmanager->Addr[P->Front];
    P->Front = (P->Front+1)%P->Size;

    return temp;
}

/************************************************************************************************
 *       @name   SqQueue_Distory
 *       @brief  释放循环队列内存以及元素内存
 *       @param  Pmanager: 指记录循环队列属性的结构体指针
 *       @param  Paddr: 指记录循环队列里面元素的指针
 *       @note   None
 *       @retval 函数调用成功，返回true，调用失败，返回false
 *************************************************************************************************/
bool SqQueue_Destory(SqQueue_p Pmanager, DataType_t *Paddr)
{
    // 备份指针
    SqQueue_p P = Pmanager;

    // 1.释放循环队列的堆内存
    free(Pmanager);
    free(Paddr);
    // 2.释放循环队列的数据元素的堆内存
    return true;
};

/**
 * @name:   main
 * @brief:  主函数
 * @param   None
 * @note:   None
 * @retval: 调用成功，返回0，调用失败，返回-1
 */
int main(int argc, char const *argv[])
{
    // 1、创建循环队列
    SqQueue_p sq_queue_p = SqQueue_Create(6);

    // 2、向尾部插入元素
    SqQueue_Enqueue(sq_queue_p, 1);
    SqQueue_Enqueue(sq_queue_p, 3);
    SqQueue_Enqueue(sq_queue_p, 5);
    SqQueue_Enqueue(sq_queue_p, 7);
    SqQueue_Enqueue(sq_queue_p, 9);

    // 4、遍历循环队列
    printf("弹出的元素：%d\n", SqQueue_Dequeue(sq_queue_p));
    printf("弹出的元素：%d\n", SqQueue_Dequeue(sq_queue_p));
    printf("弹出的元素：%d\n", SqQueue_Dequeue(sq_queue_p));
    printf("弹出的元素：%d\n", SqQueue_Dequeue(sq_queue_p));
    printf("弹出的元素：%d\n", SqQueue_Dequeue(sq_queue_p));

    // 释放内存
    SqQueue_Destory(sq_queue_p, sq_queue_p->Addr);
    return 0;
}
